﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using FarseerGames.FarseerPhysics.Dynamics;
using FarseerGames.FarseerPhysics.Dynamics.Joints;
using FarseerGames.FarseerPhysics.Dynamics.Springs;
using FarseerGames.FarseerPhysics.Mathematics;
using System.Windows.Media.Imaging;
using System.Windows.Media;
using System.Windows;
using SharpSvn;
using System.Collections.ObjectModel;
using log4net;
using FarseerGames.FarseerPhysics.Factories;
using FarseerGames.FarseerPhysics;
using FarseerGames.FarseerPhysics.Collisions;
using GraphicalSVN.GraphicSource.Dto;
using System.Threading;
using System.Text.RegularExpressions;

namespace GraphicalSVN.GraphicSource
{
    public delegate void MyUpdateHandler(SVNFrameData frameData);

    public class GraphicalSource : IGraphicalSource
    {
        private readonly GeneratorInfo generatorInfo;
        private readonly Color White = Color.FromArgb(255, 255, 255, 255);
        private readonly Color Black = Color.FromArgb(255, 0, 0, 0);
        private Point seedPoint;
        private Point MaxPoint;
        private readonly ILog logger = LogManager.GetLogger(typeof(GraphicalSource));
        private List<IObserver> observers = new List<IObserver>();
        private SVNFrameData frameData = new SVNFrameData();
        private SVNFrameData returnFrameData = new SVNFrameData();
        private int frameCount = 0;
        private List<RevisionItem> revisionList = new List<RevisionItem>();
        private DateTime startDate;
        private DateTime currentDate;
        private DateTime endDate;
        private TimeSpan dateDiff;
        private Object objectLock = new object();
        private Queue<SVNObject> queueItems = new Queue<SVNObject>();
        int frames;
        float realTimeSecondsPerFrame;
        List<SVNObject> svnObjects;

        public GraphicalSource(GeneratorInfo generatorInfo)
        {
            logger.Debug("Loading GeneratorInfo into local variable");
            this.generatorInfo = generatorInfo;
            this.seedPoint = new Point(this.generatorInfo.Width / 2, this.generatorInfo.Height / 2);
            this.MaxPoint = new Point(this.generatorInfo.Width, this.generatorInfo.Height);
        }

        public void Generate()
        {
            try
            {
                svnObjects = new List<SVNObject>();
                Collection<SvnLogEventArgs> logList = new Collection<SvnLogEventArgs>();

                logger.Debug("Generate Object Tree");
                GenerateTree(svnObjects, logList);

                int treecount = svnObjects.Count;
                logger.Info(string.Format("{0}: Objects found", treecount));

                logger.Debug("Flag Obects that are Directories to IsDirectory");
                MarkDirectories(svnObjects);

                logger.Debug("Order objects by directory");
                svnObjects = svnObjects.OrderBy(x => x.ParentDirectory).ThenBy(x => x.ObjectPath).ToList();

                var svnObject = svnObjects[0];

                if (svnObject.ParentDirectory != svnObject.ObjectPath)
                {
                    SVNObject rootDirectory = new SVNObject()
                    {
                        ObjectPath = svnObject.ParentDirectory,
                        ParentDirectory = svnObject.ParentDirectory,
                        DoesExist = true,
                    };

                    rootDirectory.Revisions.Add(new SVNRevision()
                    {
                        RevisionDate = svnObject.Revisions[0].RevisionDate,
                        RevisionNumber = svnObject.Revisions[0].RevisionNumber,
                        Action = SvnChangeAction.Add
                    });

                    svnObjects.Insert(0, rootDirectory);
                }

                logger.Debug("Generate List of Revisions");
                revisionList = GetRevisionList(svnObjects);

                startDate = revisionList.Select(x => x.RevisionDate).First();
                endDate = revisionList.Select(x => x.RevisionDate).Last();


                logger.Debug(string.Format("ProjectDateRange: {0} - {1}", startDate, endDate));

                dateDiff = endDate - startDate;

                frames = (int)Math.Ceiling(dateDiff.TotalDays * this.generatorInfo.SecPerDay * this.generatorInfo.FPS);
                realTimeSecondsPerFrame = (float)dateDiff.TotalSeconds / frames;
                currentDate = startDate;
                logger.Debug("Frames:" + frames);
                logger.Debug("realTimePerFrame:" + realTimeSecondsPerFrame);
            }
            catch (ArgumentNullException ex)
            {
                throw ex;
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

        private void ExtractRevisionInformationForCurrentState(RevisionItem item, SVNObject svnItem)
        {
            SVNRevision revision = GetRevision(item, svnItem);

            svnItem.Action = revision.Action;
            if (svnItem.Action != SvnChangeAction.Delete)
            {
                svnItem.DoesExist = true;
            }
            else
            {
                svnItem.DoesExist = false;
            }
        }

        private SVNRevision GetRevision(RevisionItem item, SVNObject svnItem)
        {
            var revision = (from x in svnItem.Revisions
                            where x.RevisionNumber == item.RevisionNumber
                            select x).Single();
            return revision;
        }

        private List<SVNObject> GetSelectedObjects(List<SVNObject> SVNObjects, RevisionItem item)
        {
            var selectedObjects = (from x in SVNObjects
                                   from y in x.Revisions
                                   where y.RevisionDate == item.RevisionDate
                                   select x).ToList();
            return selectedObjects;
        }

        private List<RevisionItem> GetRevisionList(List<SVNObject> SVNObjects)
        {
            List<RevisionItem> revisionList = (from x in SVNObjects
                                               from y in x.Revisions
                                               group y by y.RevisionDate into z
                                               select new RevisionItem { RevisionDate = z.Select(zz => zz.RevisionDate).First(), RevisionNumber = z.Select(zz => zz.RevisionNumber).First() }
                                           ).OrderBy(x => x.RevisionDate).ToList();
            return revisionList;
        }

        private void MarkDirectories(List<SVNObject> SVNObjects)
        {
            foreach (SVNObject item in SVNObjects)
            {
                if (SVNObjects
                    .Any(x => x.ObjectPath != item.ObjectPath &&
                        x.ObjectPath.StartsWith(item.ObjectPath + "/")))
                {
                    item.IsDirectory = true;
                }
            }

            // Regex re = new Regex(@"\w*\.\w*[^/]$");
            if (SVNObjects.Any(x => x.ObjectPath == "/"))
            {
                SVNObjects.Where(x => x.ObjectPath == "/").Single().IsDirectory = true;
            }
        }

        private void GenerateTree(List<SVNObject> SVNObjects, Collection<SvnLogEventArgs> logList)
        {
            if (GetSvnLog(out logList))
            {
                foreach (var item in logList)
                {
                    new TreeViewGenerator().StoreTreeInfo(item, SVNObjects);
                }
            }
        }

        private bool GetSvnLog(out Collection<SvnLogEventArgs> logList)
        {
            var client = new SvnClient();
            return client.GetLog(new Uri(this.generatorInfo.Repository), out logList);
        }

        public SVNFrameData Update(TimeSpan span)
        {
            lock (objectLock)
            {
                Interlocked.Increment(ref frameCount);
                if (revisionList.Count == 0)
                {
                    return returnFrameData;
                }

                if (currentDate > endDate)
                {
                    if (queueItems.Count != 0)
                    {
                        ActionQueueItems();
                    }

                    return returnFrameData;
                }

                DateTime nextDate = currentDate.AddSeconds(realTimeSecondsPerFrame);

                DateTime skipDate = currentDate.AddDays(1);

                var count = (from x in revisionList
                             where x.RevisionDate >= currentDate && x.RevisionDate <= skipDate
                             select x).Count();

                if (count == 0)
                {
                    nextDate = skipDate;
                }

                foreach (var item in revisionList.Where(x => x.RevisionDate >= currentDate && x.RevisionDate <= nextDate))
                {
                    var returnFrameDataArray = returnFrameData.Items.Select((svn, index) => new { Index = index, Item = svn }).ToList();
                    var revisionDate = item.RevisionDate;
                    TimeSpan diff = revisionDate - startDate;
                    int frame = (int)Math.Ceiling((1 / realTimeSecondsPerFrame) * diff.TotalSeconds);

                    logger.Info(string.Format("Frame:{0}:RevisionDate:{1}:RevisionNumber{2}", frame, item.RevisionDate, item.RevisionNumber));

                    List<SVNObject> selectedObjects = GetSelectedObjects(svnObjects, item);

                    logger.Debug("Extract Revision Information For Current State");
                    foreach (var svnItem in selectedObjects.Where(x => CheckFilters(x.ObjectPath) && x.IsDirectory))
                    {
                        ExtractRevisionInformationForCurrentState(item, svnItem);

                        if (svnItem.DoesExist)
                        {
                            var searchForItem = returnFrameDataArray.Where(x => x.Item.ObjectPath == svnItem.ObjectPath).SingleOrDefault();
                            if (searchForItem == null)
                            {
                                queueItems.Enqueue(svnItem);
                            }
                        }
                        else
                        {
                            var searchForItem = returnFrameDataArray.Where(x => x.Item.ObjectPath == svnItem.ObjectPath).SingleOrDefault();
                            if (searchForItem == null)
                            {
                                continue;
                            }
                            else
                            {
                                queueItems.Enqueue(svnItem);
                            }
                        }
                    }

                    returnFrameData.FrameNumber = frame;
                    returnFrameData.RevisionDate = nextDate;
                    returnFrameData.RevisionNumber = item.RevisionNumber;
                    
                    //frameData.AddItems(svnObjects.Where(x => x.DoesExist == true).ToList());
                    logger.Info("Generate Object Posisions for frame: " + frameData.FrameNumber);
                }
                ActionQueueItems();

                frameData.RevisionDate = currentDate;
                currentDate = nextDate;

                return returnFrameData;
            }
        }

        private void ActionQueueItems()
        {
            int modulus = frameCount % 20;
            if (modulus == 0)
            {
                SVNObject frameItem = null;
                if (queueItems.Count != 0)
                {
                    frameItem = queueItems.Dequeue();
                }
                if (frameItem != null)
                {
                    if (frameItem.DoesExist)
                    {
                        var itemIndex = returnFrameData.Items.Select((item, index) => new { Index = index, Item = item }).Where(x => x.Item.ObjectPath == frameItem.ObjectPath).SingleOrDefault();
                        if (itemIndex == null)
                        {
                            returnFrameData.Items.Add(frameItem);
                        }
                    }
                    else
                    {
                        var itemIndex = returnFrameData.Items.Select((item, index) => new { Index = index, Item = item }).Where(x => x.Item.ObjectPath == frameItem.ObjectPath).SingleOrDefault();
                        if (itemIndex != null)
                        {
                            returnFrameData.Items.RemoveAt(itemIndex.Index);
                        }
                    }
                }
            }
        }

        private bool CheckFilters(string filename)
        {
            var filter = generatorInfo.Filter;
            var filters = filter.Split(' ');
            for (int i = 0; i < filters.Length; i++)
			{
                if (Regex.IsMatch(filename, filters[i], RegexOptions.IgnoreCase))
                {
                    return false;
                }
			}
            return true;
            
        }
    }
}

